package main

import (
	"fmt"
		"math/rand"
		)

const (
	win            = 100 // The winning score in a game of Pig
		gamesPerSeries = 10  // The number of games per series to simulate
		)

// A score includes scores accumulated in previous turns for each player,
// as well as the points scored by the current player in this turn.
type score struct {
			player, opponent, thisTurn int
}

// An action transitions stochastically to a resulting score.
type action func(current score) (result score, turnIsOver bool)

// roll returns the (result, turnIsOver) outcome of simulating a die roll.
// If the roll value is 1, then thisTurn score is abandoned, and the players'
// roles swap.  Otherwise, the roll value is added to thisTurn.
func roll(s score) (score, bool) {
			outcome := rand.Intn(6) + 1 // A random int in [1, 6]
				if outcome == 1 {
								return score{s.opponent, s.player, 0}, true
									}
										return score{s.player, s.opponent, outcome + s.thisTurn}, false
}

// stay returns the (result, turnIsOver) outcome of staying.
// thisTurn score is added to the player's score, and the players' roles swap.
func stay(s score) (score, bool) {
			return score{s.opponent, s.player + s.thisTurn, 0}, true
}

// A strategy chooses an action for any given score.
type strategy func(score) action

// stayAtK returns a strategy that rolls until thisTurn is at least k, then stays.
func stayAtK(k int) strategy {
			return func(s score) action {
							if s.thisTurn >= k {
												return stay
														}
																return roll
																	}
}

// play simulates a Pig game and returns the winner (0 or 1).
func play(strategy0, strategy1 strategy) int {
			strategies := []strategy{strategy0, strategy1}
				var s score
					var turnIsOver bool
						currentPlayer := rand.Intn(2) // Randomly decide who plays first
							for s.player+s.thisTurn < win {
											action := strategies[currentPlayer](s)
													s, turnIsOver = action(s)
															if turnIsOver {
																				currentPlayer = (currentPlayer + 1) % 2
																						}
																							}
																								return currentPlayer
}

// roundRobin simulates a series of games between every pair of strategies.
func roundRobin(strategies []strategy) ([]int, int) {
			wins := make([]int, len(strategies))
				for i := 0; i < len(strategies); i++ {
								for j := i + 1; j < len(strategies); j++ {
													for k := 0; k < gamesPerSeries; k++ {
																			winner := play(strategies[i], strategies[j])
																							if winner == 0 {
																														wins[i]++
																																		} else {
																																									wins[j]++
																																													}
																																																}
																																																		}
																																																			}
																																																				gamesPerStrategy := gamesPerSeries * (len(strategies) - 1) // no self play
																																																					return wins, gamesPerStrategy
}

// ratioString takes a list of integer values and returns a string that lists
// each value and its percentage of the sum of all values.
// e.g., ratios(1, 2, 3) = "1/6 (16.7%), 2/6 (33.3%), 3/6 (50.0%)"
func ratioString(vals ...int) string {
			total := 0
				for _, val := range vals {
								total += val
									}
										s := ""
											for _, val := range vals {
															if s != "" {
																				s += ", "
																						}
																								pct := 100 * float64(val) / float64(total)
																										s += fmt.Sprintf("%d/%d (%0.1f%%)", val, total, pct)
																											}
																												return s
}

func main() {
			strategies := make([]strategy, win)
				for k := range strategies {
								strategies[k] = stayAtK(k + 1)
									}
										wins, games := roundRobin(strategies)

											for k := range strategies {
															fmt.Printf("Wins, losses staying at k =% 4d: %s\n",
																		k+1, ratioString(wins[k], games-wins[k]))
																}
}
